import {
  Component,
  OnInit,
  Input,
  Output,
  EventEmitter,
  SimpleChanges,
  ViewChild,
  ElementRef,
  TemplateRef,
  NgZone,
  OnChanges,
  OnDestroy,
  ChangeDetectorRef,
} from '@angular/core';
import { Subject } from 'rxjs';
import { auditTime } from 'rxjs/operators';
import { TypeUtils, DateService, LvConfig } from '../core/api';
import { ResizeObserverService } from '../core/services/resize-observer.service';
import { ThemeType } from '../core/tooltipbase/api';
import { TooltipDirective } from '../tooltip/api';
import { EventEnum, ValueEnum } from './interface';

@Component({
  selector: 'lv-time-slider',
  templateUrl: './timeslider.component.html',
})
export class TimesliderComponent implements OnInit, OnChanges, OnDestroy {
  @Input() lvStyle; // 时间轴样式，暂不开放

  @Input() lvSpace: number = 60; // 最大间距，用户可根据文字宽度设定
  @Input() lvRange: [Date, Date]; // 显示范围
  @Input() lvValue: [Date, Date]; // [start, end]
  @Input() lvSpeed: number = 0.2; // 缩放系数
  @Input() lvStep: [number, string] = [1, 'Hour']; // [每隔几个显示, 定义显示时、分、秒]
  @Input() lvTooltip: boolean | TemplateRef<void>;
  @Output() lvValueChange: EventEmitter<any> = new EventEmitter(); // move时触发
  @Output() lvSliderDragEnd: EventEmitter<any> = new EventEmitter(); // 拖动结束触发
  @Input() lvFormat: string | ((date: Date) => string) = 'MM-dd HH:mm'; // 日期的显示格式
  @ViewChild(TooltipDirective, { static: false }) tooltip: TooltipDirective;
  @Input() lvTimezoneOffset: number = LvConfig.timeSliderOptions.lvTimezoneOffset; // 本地时间与格林威治标准时间 (GMT) 的分钟差
  @ViewChild('timeSliderCanvas', { static: true }) timeSliderCanvas: ElementRef<any>;
  @ViewChild('timeSliderCanvasContainer', { static: true }) timeSliderCanvasContainer: ElementRef<any>;
  @Input('lvTooltipTheme') lvTheme: ThemeType =
    LvConfig.timeSliderOptions.lvTheme || LvConfig.tooltipOptions.lvTheme || 'dark'; // 主题颜色

  zoom = 1; // 缩放比例
  handleEnd; // 右侧滑块
  minZoom = 1;
  handleStart; // 左侧滑块
  msOffset = 0; // 偏移时间
  leftTrackWidth; // 左滑块宽度
  leftTrackColor; // 左滑块颜色
  canvasW: number;
  canvasH: number;
  pxPerms: number; // 每毫秒占多少px
  rightTrackWidth; // 右滑块宽度
  rightTrackColor; // 右滑块颜色
  distance: number; // 每个刻度间距
  handleEndMs: any; // 右侧滑块距离轴原点的ms数
  toolTipLabel: any; // tooltip 提示文字
  handleStartMs: any; // 左滑块距离轴原点的ms数
  isMousedown = false; // 拖动mousedown标记
  isMousemove = false; // 拖动mousemove标记
  activeTrack: string; // 记录当前选中了哪个滑块
  scaleTimeArr: any[]; // render时存放每个刻度的ms
  toolTipLeft: string; // tooltip 显示隐藏

  isMouseEnter = false; // 鼠标移入标记
  preHandleEnd: number; // 记录拖动之前右侧滑块的位置

  defaultTrackWidth = 1; // 滑块描边width
  mousedownCursor = null; // 拖动mousedown的位置

  selectionDrag: boolean;

  preHandleStart: number; // 记录拖动之前左侧滑块的位置
  canvas: HTMLCanvasElement;
  actualRange: [Date, Date]; // 实际日期范围，通过时区转换
  actualValue: [Date, Date]; // 实际日期值，通过时区转换
  msOffsetHandle: number = 0; // 移动滑块时ms偏移量
  cursorType: any = 'pointer';
  public typeUtils = TypeUtils;
  cxt: CanvasRenderingContext2D;
  defaultTrackColor = '#e6e6e6'; // 默认滑块颜色
  tooltipVisible: boolean = false; // tooltip 显示隐藏

  defaultHoverTrackColor = '#4d4d4d'; // 滑块hover状态颜色
  defaultActiveTrackColor = '#2673e5'; // 滑块active状态颜色

  formatMap: Map<number, string> = new Map();
  mousePosition: { x: number; y: number } = null;
  eventType:
    | EventEnum.MOUSE_WHEEL
    | EventEnum.MOUSE_DOWN
    | EventEnum.MOUSE_MOVE
    | EventEnum.MOUSE_OVER
    | EventEnum.MOUSE_UP
    | EventEnum.MOUSE_OUT
    | null = null;

  private _resizeObserver;
  private requestAnimationId: number;
  private _mouseMove$ = new Subject<any>();

  constructor(
    protected dateService: DateService,
    private resizeObserver: ResizeObserverService,
    public ngZone: NgZone,
    private cdr: ChangeDetectorRef,
  ) {}

  /**
   * 清除canvas 每次重新绘制需要先清除
   */
  clearCanvas(): void {
    this.cxt.clearRect(0, 0, this.canvasW, this.canvasH);
  }

  /**
   * 渲染视图
   */
  render(): void {
    this.clearCanvas();
    // 画刻度尺
    this.drawRuler(this.cxt);
    // 画选区
    this.drawSelection(this.handleStart, this.handleEnd - this.handleStart);
    // 画滑块
    this.drawTrack();
  }

  /**
   * 拖动 mousedown事件
   */
  mousedownFunc(e): void {
    this.isMousedown = true;
    this.mousedownCursor = this.getMousePos(e).x; // 记住mousedown的位置
    this.eventType = EventEnum.MOUSE_DOWN;
    this.updatePosition();
    this.cursorType = 'grabbing';
  }

  /**
   * 拖动/点击 mouseup事件
   */
  mouseupFunc(): void {
    this.isMousedown = false;
    this.eventType = EventEnum.MOUSE_UP;
    this.updatePosition();
    this.isMousemove = false;
    this.activeTrack = '';
    this.selectionDrag = false;
    this.cursorType = 'pointer';
  }

  /**
   * 返回时间轴上刻度的时间
   */
  getScaleLabel(datetime): string {
    const key = datetime.getTime();
    let result = this.formatMap.get(key);
    if (!result) {
      result = this.dateService.format(datetime, this.lvFormat);
      this.formatMap.set(key, result);
    }
    return result;
  }

  /**
   * 画滑块
   * @param cxt 上下文
   */
  drawTrack(): void {
    const y = 0;
    const height = this.canvasH;
    const arr = [
      { x: this.handleStart, y, width: 10, height },
      { x: this.handleEnd, y, width: 10, height },
    ];
    for (let i = 0; i < arr.length; i++) {
      this.drawTrackFun(i, arr[i]);
    }
  }

  /**
   * 计算每几个刻度显示label（rang结束位置显示label）
   * @param min  最小显示label宽度
   * @param totalScale 总刻度数
   */
  showLabelIndex(min, totalScale): any {
    let resultIndex;
    if (totalScale % min === 0) {
      return min;
    }
    for (let index = min; index < totalScale; index++) {
      if (totalScale % index === 0) {
        resultIndex = index;
        break;
      }
    }
    return resultIndex || min; // 没有整除情况
  }

  ngOnInit(): void {
    this.init();
    // 监听 timeSliderCanvasContainer 的尺寸变化
    this._resizeObserver = this.resizeObserver.observe(this.timeSliderCanvasContainer.nativeElement, (): void => {
      this.canvasW = this.timeSliderCanvasContainer.nativeElement.clientWidth; // 自适应宽度
      this.updateRange();
    });
  }

  /**
   * 画刻度线
   * @param cxt 上下文
   * @param graduationLeft x距离
   * @param y 从y轴什么地方开始画
   * @param toy 刻度线位置
   */
  drawScale(cxt, graduationLeft, y, toy, color): void {
    cxt.beginPath();
    cxt.save();
    cxt.strokeStyle = color ? color : '#f2f2f2';
    cxt.lineWidth = 1;
    cxt.lineCap = 'round';
    cxt.moveTo(graduationLeft, y);
    cxt.lineTo(graduationLeft, Math.floor(toy));

    cxt.stroke();
    cxt.restore();
    cxt.closePath();
  }

  /**
   * 获取偏移时区后的时间
   */
  getTimezoneOffsetRangeDate(value, reverse = false): any {
    if (this.lvTimezoneOffset) {
      return [
        this.dateService.getTimezoneOffsetDate(value[0], this.lvTimezoneOffset, reverse),
        this.dateService.getTimezoneOffsetDate(value[1], this.lvTimezoneOffset, reverse),
      ];
    } else {
      return value;
    }
  }

  /**
   * 解决刻度和文字模糊问题
   */
  handleDevicePixelRatio(): void {
    if (window.devicePixelRatio) {
      this.canvas.width = window.devicePixelRatio * this.canvasW;
      this.canvas.height = window.devicePixelRatio * this.canvasH;
      this.cxt.scale(window.devicePixelRatio, window.devicePixelRatio);
      this.canvas.style.transform = `scale(${1 / window.devicePixelRatio})`;
      this.canvas.style.transformOrigin = 'center top';
    }
  }

  /**
   * 获取鼠标坐标
   */
  getMousePos(e): { x: number; y: number } {
    const rect = this.canvas.getBoundingClientRect();
    const button = { left: 0, middle: 1, right: 2 };
    if (e.button === button.right) {
      return null;
    } // skip right mouse click
    if (e.button === button.middle) {
      return null;
    } // skip middle mouse click

    if (!rect) {
      return null;
    }

    return {
      x: e.clientX - rect.left,
      y: e.clientY - rect.top,
    };
  }

  /**
   * tips位置更新
   * @param type 类型
   */
  updateTooltip(type): void {
    if (this.lvTooltip === false) {
      return;
    }
    const toolTipArr = this.updateLvValue(ValueEnum.TOOLTIP);
    const start = this.handleStart < this.handleEnd ? toolTipArr[0] : toolTipArr[1];
    const end = this.handleStart < this.handleEnd ? toolTipArr[1] : toolTipArr[0];
    this.toolTipLabel = type === 'start' ? start : end;
    this.toolTipLeft = type === 'start' ? this.handleStart : this.handleEnd;
    this.tooltip.updatePosition();
  }

  /**
   * 画选区
   */
  drawSelection(x, width): void {
    const y = 0;
    const height = this.canvasH;
    this.cxt.globalAlpha = 1;
    this.cxt.beginPath();
    this.cxt.rect(x, y, width, height);
    this.cxt.globalAlpha = 0.3; // ie浏览器添加选区透明度
    this.cxt.fillStyle = '#2673e5';
    this.cxt.fill();
    if (this.eventType === EventEnum.MOUSE_MOVE || this.eventType === EventEnum.MOUSE_DOWN) {
      this.selectionDrag = !!(this.mousePosition && this.cxt.isPointInPath(this.mousePosition.x, this.mousePosition.y));
    }
  }

  onMouseDown(key: 0 | 1): void {
    // 鼠标按下
    if (this.mousePosition && this.cxt.isPointInPath(this.mousePosition.x, this.mousePosition.y)) {
      if (key === 0) {
        this.activeTrack = 'start';
        this.leftTrackWidth = 3;
        this.leftTrackColor = this.defaultActiveTrackColor;
      } else if (key === 1) {
        this.activeTrack = 'end';
        this.rightTrackWidth = 3;
        this.rightTrackColor = this.defaultActiveTrackColor;
      }
      this.cursorType = 'e-resize';
    }
  }

  /**
   * 查找最近的时间刻度
   * @param iVal 当前鼠标拖动的位置ms
   * @param iTickValues 初始化时存起来的刻度ms
   */
  snapFromTickvalues(iVal, iTickValues): any {
    if (iTickValues.length === 0) {
      return iVal;
    }
    if (iVal <= iTickValues[0]) {
      return iTickValues[0];
    }
    if (iVal >= iTickValues[iTickValues.length - 1]) {
      return iTickValues[iTickValues.length - 1];
    }
    let d = Infinity;
    let snapValue;
    for (const item of iTickValues) {
      const tick = item;
      if (d > Math.abs(iVal - tick)) {
        snapValue = tick;
        d = Math.abs(iVal - tick);
      }
    }
    return snapValue;
  }

  /**
   * 鼠标移出事件
   */
  mouseoutFunc(): void {
    this.isMouseEnter = false;
    this.eventType = EventEnum.MOUSE_OUT;
    this.updatePosition();
    this.isMousemove = false;
    this.activeTrack = '';
    this.selectionDrag = false;
    this.isMousedown = false;
    this.rightTrackWidth = this.defaultTrackWidth;
    this.leftTrackWidth = this.defaultTrackWidth;
    this.rightTrackColor = this.defaultTrackColor;
    this.leftTrackColor = this.defaultTrackColor;
    this.ngZone.run((): void => {
      this.tooltipVisible = false;
      this.cdr.markForCheck();
    });
  }

  ngOnDestroy(): void {
    this._resizeObserver.unobserve();
    cancelAnimationFrame(this.requestAnimationId);
    this.canvas.removeEventListener(EventEnum.MOUSE_WHEEL, this.mousewheelFunc.bind(this));
    this.canvas.removeEventListener(EventEnum.MOUSE_SCROLL, this.mousewheelFunc.bind(this)); // firefox绑定滚轮事件
    this.canvas.removeEventListener(EventEnum.MOUSE_DOWN, this.mousedownFunc.bind(this));
    this.canvas.removeEventListener(EventEnum.MOUSE_MOVE, this.mousemoveFunc.bind(this));
    this.canvas.removeEventListener(EventEnum.MOUSE_UP, this.mouseupFunc.bind(this));
    this.canvas.removeEventListener(EventEnum.MOUSE_OUT, this.mouseoutFunc.bind(this));
  }

  /**
   * 滑块位置计算
   * @param type 类型
   */
  setHandlePos(type): void {
    if (this.activeTrack === 'start') {
      // 拖动左滑块
      this.handleStartMs = this.handleStartMs + this.msOffsetHandle;
      this.handleStart = this.mousedownCursor;
      this.updateTooltip('start');
    } else if (this.activeTrack === 'end') {
      // 拖动右滑块
      this.handleEndMs = this.handleEndMs + this.msOffsetHandle;
      this.handleEnd = this.mousedownCursor;
      this.updateTooltip('end');
    } else {
      // 没拖动滑块,拖动或者缩放刻度尺
      if (type === EventEnum.MOUSE_MOVE || type === EventEnum.MOUSE_WHEEL) {
        this.handleStart = (this.handleStartMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;
        this.handleEnd = (this.handleEndMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;
      }
    }
  }

  /**
   * 将滑块位置ms转化为date
   * @param type 0: 传出Date格式； 1: tooltip显示格式化后的文本
   */
  updateLvValue(type): Date[] | string[] {
    let startDate;
    let endDate;
    if (this.handleEndMs - this.handleStartMs > 0) {
      startDate = this.snapFromTickvalues(this.handleStartMs, this.scaleTimeArr);
      endDate = this.snapFromTickvalues(this.handleEndMs, this.scaleTimeArr);
    } else {
      startDate = this.snapFromTickvalues(this.handleEndMs, this.scaleTimeArr);
      endDate = this.snapFromTickvalues(this.handleStartMs, this.scaleTimeArr);
    }
    if (type === ValueEnum.DATE) {
      return [new Date(startDate + this.actualRange[0].getTime()), new Date(endDate + this.actualRange[0].getTime())];
    } else {
      return [
        this.getScaleLabel(new Date(startDate + this.actualRange[0].getTime())),
        this.getScaleLabel(new Date(endDate + this.actualRange[0].getTime())),
      ];
    }
  }

  /**
   * 位置更新
   */
  updatePosition(): void {
    const type = this.eventType;
    if (!type) {
      return;
    }
    const isDragging =
      (type === EventEnum.MOUSE_UP || type === EventEnum.MOUSE_OUT) &&
      this.isMousemove &&
      (this.activeTrack || this.selectionDrag);
    if (isDragging) {
      this.setFinalHandle();
      if (!this.selectionDrag) {
        this.rightTrackWidth = this.defaultTrackWidth;
        this.leftTrackWidth = this.defaultTrackWidth;
        this.rightTrackColor = this.defaultTrackColor;
        this.leftTrackColor = this.defaultTrackColor;
      }
      const _rangeDate = this.getTimezoneOffsetRangeDate(this.updateLvValue(ValueEnum.DATE));
      this.lvValueChange.emit(_rangeDate);
    } else if (type === EventEnum.MOUSE_MOVE || type === EventEnum.MOUSE_WHEEL) {
      this.setHandlePos(type);
    }
  }

  /**
   * 设置最终滑块位置
   */
  setFinalHandle(): void {
    if (this.activeTrack === 'start' || this.selectionDrag) {
      this.handleStartMs = this.snapFromTickvalues(this.handleStartMs, this.scaleTimeArr);
      this.handleStart = (this.handleStartMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;
      this.updateTooltip('start');
    }
    if (this.activeTrack === 'end' || this.selectionDrag) {
      this.handleEndMs = this.snapFromTickvalues(this.handleEndMs, this.scaleTimeArr);
      this.handleEnd = (this.handleEndMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;
      this.updateTooltip('end');
    }
    if (this.handleStart !== this.preHandleStart || this.handleEnd !== this.preHandleEnd) {
      const _rangeDate = this.getTimezoneOffsetRangeDate(this.updateLvValue(ValueEnum.DATE));
      this.lvSliderDragEnd.emit(_rangeDate);
    }
  }

  /**
   * 输入更新后执行初始化
   */
  updateRange(): void {
    this.actualRange = this.getTimezoneOffsetRangeDate(this.lvRange, true);
    this.actualValue = this.getTimezoneOffsetRangeDate(this.lvValue, true);

    this.pxPerms = (this.canvasW - this.lvSpace) / (this.actualRange[1].getTime() - this.actualRange[0].getTime());

    this.handleStartMs = this.handleStartMs
      ? this.handleStartMs
      : this.actualValue[0].getTime() - this.actualRange[0].getTime();
    this.handleStart = (this.handleStartMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;

    this.handleEndMs = this.handleEndMs
      ? this.handleEndMs
      : this.actualValue[1].getTime() - this.actualRange[0].getTime();
    this.handleEnd = (this.handleEndMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;

    this.canvas.width = this.canvasW;
    this.canvas.height = this.canvasH;
  }

  onMouseOver(key: 0 | 1): void {
    // 鼠标move且鼠标没有按下
    this.rightTrackWidth = this.defaultTrackWidth;
    this.leftTrackWidth = this.defaultTrackWidth;
    this.rightTrackColor = this.defaultTrackColor;
    this.leftTrackColor = this.defaultTrackColor;
    if (this.mousePosition && this.cxt.isPointInPath(this.mousePosition.x, this.mousePosition.y)) {
      if (key === 0) {
        this.activeTrack = 'start';
        this.leftTrackWidth = 3;
        this.leftTrackColor = this.defaultHoverTrackColor;
        this.updateTooltip('start');
      } else if (key === 1) {
        this.activeTrack = 'end';
        this.rightTrackWidth = 3;
        this.rightTrackColor = this.defaultHoverTrackColor;
        this.updateTooltip('end');
      }
      this.cursorType = 'e-resize';
      if (this.lvTooltip !== false) {
        this.ngZone.run((): void => {
          this.tooltipVisible = true;
          this.cdr.markForCheck();
        });
      }
    } else if (!(this.activeTrack === 'start' && key === 1)) {
      this.activeTrack = '';
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (this.canvas && !this.isMouseEnter) {
      if (changes.lvRange || changes.lvValue) {
        this.actualRange = changes.lvRange
          ? this.getTimezoneOffsetRangeDate(changes.lvRange.currentValue, true)
          : this.actualRange;
        this.actualValue = changes.lvValue
          ? this.getTimezoneOffsetRangeDate(changes.lvValue.currentValue, true)
          : this.actualValue;

        this.pxPerms = (this.canvasW - this.lvSpace) / (this.actualRange[1].getTime() - this.actualRange[0].getTime());
        this.handleStartMs = this.actualValue[0].getTime() - this.actualRange[0].getTime();
        this.handleEndMs = this.actualValue[1].getTime() - this.actualRange[0].getTime();
      }
      if (changes.lvRange) {
        this.zoom = 1;
        this.msOffset = 0;
      }
      this.updateRange();

      const _rangeDate = this.getTimezoneOffsetRangeDate(this.updateLvValue(ValueEnum.DATE));
      this.lvValueChange.emit(_rangeDate);
    }
    if (changes.lvFormat) {
      this.formatMap.clear();
    }
  }

  /**
   * 获取刻度总格数
   */
  getDivision(): any {
    const rangeStart = this.actualRange[0];
    const rangeEnd = this.actualRange[1];
    const dateDiff = rangeEnd.getTime() - rangeStart.getTime();
    const yearDiff = Math.floor(rangeEnd.getFullYear() - rangeStart.getFullYear()); // 计算出相差年数
    const monthDiff = Math.floor(yearDiff * 12 + rangeEnd.getMonth() - rangeStart.getMonth()); // 计算出相差月数
    const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); // 计算出相差天数
    const hourDiff = Math.floor(dateDiff / (3600 * 1000)); // 计算出相差小时数
    const minuteDiff = Math.floor(dateDiff / (60 * 1000)); // 计算出相差分钟数
    let division;
    switch (this.lvStep[1]) {
      case 'Year':
        division = yearDiff / this.lvStep[0];
        break;
      case 'Month':
        division = monthDiff / this.lvStep[0];
        break;
      case 'Day':
        division = dayDiff / this.lvStep[0];
        break;
      case 'Hour':
        division = hourDiff / this.lvStep[0];
        break;
      case 'Minute':
        division = minuteDiff / this.lvStep[0];
        break;
      default:
        division = minuteDiff / this.lvStep[0];
        break;
    }
    return division;
  }

  /**
   *
   * @param key i
   * @param item arr[i]
   * @param trackW 滑块描边宽度
   * @param trackColor 滑块颜色
   * @param point 鼠标当前选中点
   */
  drawTrackFun(key, item): void {
    const height = this.canvasH;
    // 画可聚焦范围,设置宽度为12的矩形区域,方便滑块聚焦
    this.cxt.globalAlpha = 0;
    const areaWidth = 5;
    this.cxt.beginPath();
    this.cxt.moveTo(item.x - areaWidth, 0);
    this.cxt.lineTo(item.x + areaWidth, 0);
    this.cxt.lineTo(item.x + areaWidth, height);
    this.cxt.lineTo(item.x - areaWidth, height);
    this.cxt.lineTo(item.x - areaWidth, 0);
    this.cxt.closePath();
    this.cxt.fill();
    this.cxt.stroke();
    if (this.eventType === EventEnum.MOUSE_OVER) {
      this.onMouseOver(key);
    } else if (this.eventType === EventEnum.MOUSE_DOWN) {
      this.onMouseDown(key);
    }
    // 画实际滑块
    this.cxt.globalAlpha = 1;
    this.cxt.beginPath();
    this.cxt.moveTo(item.x, 0);
    this.cxt.lineTo(item.x, height);
    this.cxt.strokeStyle = key === 0 ? this.leftTrackColor : this.rightTrackColor;
    this.cxt.fillStyle = key === 0 ? this.leftTrackColor : this.rightTrackColor;
    this.cxt.lineWidth = key === 0 ? this.leftTrackWidth : this.rightTrackWidth;
    this.cxt.closePath();
    this.cxt.fill();
    this.cxt.stroke();

    this.timeSliderCanvas.nativeElement.style.cursor = this.cursorType;
  }

  /**
   * 滚轮放大缩小
   */
  mousewheelFunc(event): boolean {
    if (event?.preventDefault) {
      event.preventDefault();
    } else {
      // tslint:disable-next-line:deprecation
      window.event.returnValue = false;
      return false;
    }
    // tslint:disable-next-line:deprecation
    const e = window.event || event;
    const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));

    if (delta > 0) {
      const preZoom = this.zoom;
      this.zoom += this.lvSpeed;
      const division = this.getDivision(); // 总刻度数
      const distance = ((this.canvasW - this.lvSpace) / division) * this.zoom; // 每格间距
      if (distance > this.lvSpace) {
        this.zoom = preZoom;
      }
    } else if (delta < 0) {
      // 拖动滑块时禁止刻度尺拖动
      const offsetDistance = this.msOffset * this.pxPerms * this.zoom; // 开始的偏移距离
      const rightBoundary = (this.canvasW - this.lvSpace) * this.zoom + this.lvSpace - this.canvasW + offsetDistance;
      this.zoom -= this.lvSpeed;
      if (this.zoom <= this.minZoom) {
        this.zoom = this.minZoom;
      }
      if (rightBoundary <= 0) {
        this.msOffset = (this.msOffset * this.lvSpeed) / this.zoom;
      }
    }
    this.eventType = EventEnum.MOUSE_WHEEL;
    this.updatePosition();
    return true;
  }

  /**
   * 初始化时间轴
   */
  init(): void {
    // 画图参数
    this.canvas = this.timeSliderCanvas.nativeElement as HTMLCanvasElement;
    this.cxt = this.canvas.getContext('2d');
    this.canvasW = this.timeSliderCanvasContainer.nativeElement.clientWidth; // 自适应宽度
    this.canvasH = this.canvas.getBoundingClientRect().height;
    this.rightTrackWidth = this.defaultTrackWidth;
    this.leftTrackWidth = this.defaultTrackWidth;
    this.rightTrackColor = this.defaultTrackColor;
    this.leftTrackColor = this.defaultTrackColor;
    this.updateRange();

    this.ngZone.runOutsideAngular((): void => {
      this.canvas.addEventListener(EventEnum.MOUSE_WHEEL, this.mousewheelFunc.bind(this));
      this.canvas.addEventListener(EventEnum.MOUSE_SCROLL, this.mousewheelFunc.bind(this)); // firefox绑定滚轮事件
      this.canvas.addEventListener(EventEnum.MOUSE_DOWN, this.mousedownFunc.bind(this));
      this.canvas.addEventListener(EventEnum.MOUSE_MOVE, this.mousemoveFunc.bind(this));
      this.canvas.addEventListener(EventEnum.MOUSE_UP, this.mouseupFunc.bind(this));
      this.canvas.addEventListener(EventEnum.MOUSE_OUT, this.mouseoutFunc.bind(this));

      const loop = (): void => {
        this.render();

        this.requestAnimationId = requestAnimationFrame(loop);
      };
      this.requestAnimationId = requestAnimationFrame(loop);
    });

    // 订阅move事件触发change 事件
    this._mouseMove$.pipe(auditTime(20)).subscribe((res): void => {
      this.ngZone.run((): void => {
        const _rangeDate = this.getTimezoneOffsetRangeDate(res);
        this.lvValueChange.emit(_rangeDate);
      });
    });
  }

  /**
   * 画刻度尺
   * @param cxt 画布上下文
   */
  drawRuler(cxt): void {
    const division = this.getDivision(); // 总刻度数
    this.distance = ((this.canvasW - this.lvSpace) / division) * this.zoom; // 每格间距
    const offsetDistance = this.msOffset * this.pxPerms * this.zoom; // 开始的偏移距离
    const timeDistance = (this.actualRange[1].getTime() - this.actualRange[0].getTime()) / division; // 每格ms

    const showScaleIndex = Math.ceil(3 / this.distance); // 隔几个刻度显示刻度,最小3px
    let showIndex = Math.ceil(this.lvSpace / (this.distance * showScaleIndex)); // 隔几个刻度显示label
    showIndex = this.showLabelIndex(showIndex, Math.floor(division / showScaleIndex));

    let color; // 刻度线颜色
    let scaleH;

    // 刻度尺背景
    cxt.fillStyle = this.lvStyle?.background || '#fff';
    cxt.fillRect(0, 0, this.canvasW, this.canvasH);
    this.cxt.globalAlpha = 1;
    this.scaleTimeArr = [];
    // 画刻度线
    for (let i = 0; i <= division / showScaleIndex; i++) {
      const graduationLeft = offsetDistance + this.distance * i * showScaleIndex + this.lvSpace / 2;

      color = i % showIndex === 0 ? '#e6e6e6' : '#f2f2f2';
      scaleH = i % showIndex === 0 ? 0.2 : 0.15;

      // 上面刻度线
      this.drawScale(cxt, graduationLeft, 0, this.canvasH * scaleH, color);
      // 下面刻度线
      this.drawScale(cxt, graduationLeft, this.canvasH, this.canvasH * (1 - scaleH), color);

      this.scaleTimeArr.push(i * timeDistance * showScaleIndex);

      // 添加刻度label
      if (i % showIndex === 0) {
        const date = this.actualRange[0].getTime() + i * timeDistance * showScaleIndex; // 实际的日期范围，通过时区转换 变量actualRange
        const scaleLabel = this.getScaleLabel(new Date(date));
        const labelWidth = this.cxt.measureText(scaleLabel).width;
        cxt.fillStyle = this.lvStyle?.fontColor || '#1a1a1a';
        cxt.fillText(scaleLabel, graduationLeft - labelWidth / 2, 23);
      }
    }
  }

  // 拖动/鼠标hover显示 mousemove事件
  mousemoveFunc(e): void {
    this.isMouseEnter = true;
    // 记录鼠标位置
    this.mousePosition = this.getMousePos(e);

    if (this.isMousedown) {
      this.handleMouseDown();
    } else {
      // 处理hover状态
      this.eventType = EventEnum.MOUSE_OVER;
      if (!this.activeTrack) {
        this.cursorType = 'grab';
        this.ngZone.run((): void => {
          this.tooltipVisible = false;
          this.cdr.markForCheck();
        });
      }
    }
    this.updatePosition();
    this._mouseMove$.next(this.updateLvValue(ValueEnum.DATE));
  }

  handleMouseDown(): void {
    // 鼠标按下拖动之前记录滑块位置
    if (!this.isMousemove) {
      this.preHandleStart = this.handleStart;
      this.preHandleEnd = this.handleEnd;
    }
    // 拖动
    const diffX = this.mousePosition.x - this.mousedownCursor;
    // 拖动选区
    if (this.selectionDrag && !this.activeTrack) {
      // 左滑块不能超过第一个刻度
      const leftBoundary = this.msOffset * this.pxPerms * this.zoom + this.lvSpace / 2;
      // 右滑块不能超过最后一个刻度
      const rightBoundary = this.scaleTimeArr[this.scaleTimeArr.length - 1];

      const handleStartMs = this.handleStartMs + diffX / (this.pxPerms * this.zoom);
      const handleStart = (this.handleStartMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;

      const handleEndMs = this.handleEndMs + diffX / (this.pxPerms * this.zoom);
      const handleEnd = (this.handleEndMs + this.msOffset) * this.pxPerms * this.zoom + this.lvSpace / 2;
      // 处理左右滑块位置交换场景
      const handleLeft = this.handleEndMs - this.handleStartMs > 0 ? handleStart : handleEnd;
      const handleRightMs = this.handleEndMs - this.handleStartMs > 0 ? handleEndMs : handleStartMs;

      const flag = (diffX > 0 || handleLeft > leftBoundary) && (diffX < 0 || handleRightMs < rightBoundary);
      if (flag) {
        this.handleStartMs = handleStartMs;
        this.handleEndMs = handleEndMs;
        this.handleStart = handleStart;
        this.handleEnd = handleEnd;
        this.eventType = EventEnum.MOUSE_MOVE;
      }
    } else if (!this.activeTrack) {
      // 拖动滑块时禁止刻度尺拖动
      const offsetDistance = this.msOffset * this.pxPerms * this.zoom; // 开始的偏移距离
      const rightBoundary = (this.canvasW - this.lvSpace) * this.zoom + this.lvSpace - this.canvasW + offsetDistance;
      // 左右边界拖动限制
      const flag =
        (this.msOffset + diffX / (this.pxPerms * this.zoom) < 0 && diffX > 0) || (rightBoundary > 0 && diffX < 0);
      if (flag) {
        this.msOffset = this.msOffset + diffX / (this.pxPerms * this.zoom);
      }
    } else {
      this.msOffsetHandle = diffX / (this.pxPerms * this.zoom); // 移动滑块时ms偏移量
    }
    this.eventType = EventEnum.MOUSE_MOVE;
    this.isMousemove = true;
    this.mousedownCursor = this.mousePosition.x;
  }
}
